<?php
// +----------------------------------------------------------------------
// | 在我们年轻的城市里，没有不可能的事！
// +----------------------------------------------------------------------
// | Copyright (c) 2020 http://srs.micang.com All rights reserved.
// +----------------------------------------------------------------------
// | Author : Jansen <jansen.shi@qq.com>
// +----------------------------------------------------------------------
namespace jansen\srs\drivers;
use Metaregistrar\EPP\eppContact;
use Metaregistrar\EPP\eppContactHandle;
use Metaregistrar\EPP\eppContactPostalInfo;
use Metaregistrar\EPP\eppDomain;
use Metaregistrar\EPP\eppException;
use Metaregistrar\EPP\eppHost;
use Metaregistrar\EPP\eppTransferRequest;
use Metaregistrar\EPP\verisignEppCheckContactRequest;
use Metaregistrar\EPP\verisignEppCheckDomainRequest;
use Metaregistrar\EPP\verisignEppCheckHostRequest;
use Metaregistrar\EPP\verisignEppCreateContactRequest;
use Metaregistrar\EPP\verisignEppCreateDomainRequest;
use Metaregistrar\EPP\verisignEppCreateHostRequest;
use Metaregistrar\EPP\verisignEppDeleteContactRequest;
use Metaregistrar\EPP\verisignEppDeleteDomainRequest;
use Metaregistrar\EPP\verisignEppDeleteHostRequest;
use Metaregistrar\EPP\verisignEppInfoContactRequest;
use Metaregistrar\EPP\verisignEppInfoDomainRequest;
use Metaregistrar\EPP\verisignEppInfoHostRequest;
use Metaregistrar\EPP\verisignEppRealNameDomainRequest;
use Metaregistrar\EPP\verisignEppRenewDomainRequest;
use Metaregistrar\EPP\verisignEppRestoreDomainRequest;
use Metaregistrar\EPP\verisignEppTransferDomainRequest;
use Metaregistrar\EPP\verisignEppUpdateContactRequest;
use Metaregistrar\EPP\verisignEppUpdateDomainRequest;
use Metaregistrar\EPP\verisignEppUpdateHostRequest;
class Verisign extends Registry{
    public function __construct(bool $logging=true){
        parent::__construct($logging);
    }
    /**
     * @inheritDoc
     */
    public function domainCheck(string $domain):bool{
        $eppRequest = new verisignEppCheckDomainRequest(new eppDomain($domain));
        $this->request($eppRequest);
        $result = $this->response->getCheckedDomains();
        return (bool)$result[0]['available'];
    }
    /**
     * @inheritDoc
     */
    public function domainInfo(string $domain, string $password=null, string $hosts='all'):array{
        $eppRequest = new verisignEppInfoDomainRequest(new eppDomain($domain, null, null, null, 0, $password), $hosts);
        $this->request($eppRequest);
        $result = [];
        $result['roid'] = $this->response->getDomainRoid();
        $result['name'] = $this->response->getDomainName();
        $result['status'] = $this->response->getDomainStatuses();
        $result['password'] = $this->response->getDomainAuthInfo();
        $result['create_time'] = $this->response->getDomainCreateDate();
        $result['update_time'] = $this->response->getDomainUpdateDate();
        $result['expire_time'] = $this->response->getDomainExpirationDate();
        $result['contacts']['registrant'] = $this->response->getDomainRegistrant();
        $result['contacts']['admin'] = $this->response->getDomainContact('admin');
        $result['contacts']['tech'] = $this->response->getDomainContact('tech');
        $nameServers = $this->response->getDomainNameservers();
        if (is_array($nameServers)) {
            foreach ($nameServers as $nameServer) {
                $result['ns'][] = $nameServer->getHostname();
            }
        }
        $result['rgp'] = $this->response->getDomainRgps();
        $result['verify_codes'] = $this->response->getVerificationCodes();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function domainCreate(string $domain, int $period, string $contactId=null, array $ns = [], string $rnvc=null, string $dnvc=null):array{
        $eppDomain = new eppDomain($domain);
        if (!empty($contactId)){
            //设置域名持有人信息
            $eppDomain->setRegistrant(new eppContactHandle($contactId, eppContactHandle::CONTACT_TYPE_REGISTRANT));
            //设置域名管理员信息
            $eppDomain->addContact(new eppContactHandle($contactId, eppContactHandle::CONTACT_TYPE_ADMIN));
            //设置域名技术联系人信息
            $eppDomain->addContact(new eppContactHandle($contactId, eppContactHandle::CONTACT_TYPE_TECH));
        }
        //设置年限
        $eppDomain->setPeriod($period);
        //设置域名密码
        $password = $this->generatePassword(12);
        $eppDomain->setAuthorisationCode($password);
        //设置域名DNS
        if (count($ns) > 0){
            foreach($ns as $host){
                $eppDomain->addHost(new eppHost($host));
            }
        }
        $eppRequest = new verisignEppCreateDomainRequest($eppDomain, $rnvc, $dnvc, $this->isIDN($domain)?'CHI':'ENG');
        $this->request($eppRequest);
        $result = [];
        $result['name'] = $this->response->getDomainName();
        $result['password'] = $password;
        $result['create_time'] = $this->response->getDomainCreateDate();
        $result['expire_time'] = $this->response->getDomainExpirationDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function domainDelete(string $domain):bool{
        $domainInfo = $this->domainInfo($domain);
        if (in_array('clientDeleteProhibited', $domainInfo['status']) || in_array('serverDeleteProhibited', $domainInfo['status'])){
            throw new eppException('Domain cannot be deleted, status is not allowed.');
        }
        $eppRequest = new verisignEppDeleteDomainRequest(new eppDomain($domain));
        $this->request($eppRequest);
        return true;
    }
    /**
     * @inheritDoc
     */
    public function domainRenew(string $domain, string $date, int $period):array{
        $domainInfo = $this->domainInfo($domain);
        if (in_array('clientRenewProhibited', $domainInfo['status']) || in_array('serverRenewProhibited', $domainInfo['status'])){
            throw new eppException('Domain cannot be renew, status is not allowed.');
        }
        //如果域名进入自动续费期，则需要判断手动续费期限是否大于自动续费期限
        if (!empty($domainInfo['rgp']['autoRenewPeriod'])){
            if ($period == 1){
                //域名处于自动续费期，且手动续费为1年时，不得向注册局发送续费命令，直接返回自动续费的到期时间即可
                return [
                    'name'          => $domain,
                    'expire_time'   => $domainInfo['expire_time']
                ];
            }
            //域名处于自动续费期，但续费年限大于1年时，向注册局发送续费命令时，需要将续费年限减1年，并将当前域名过期日期设置为自动续费后的过期日期
            $period--;
            $date = $domainInfo['expire_time'];
        }
        $eppRequest = new verisignEppRenewDomainRequest(new eppDomain($domain, null, null, null, $period), $date);
        $this->request($eppRequest);
        $result = [];
        $result['name'] = $this->response->getDomainName();
        $result['expire_time'] = $this->response->getDomainExpirationDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function domainUpdate(string $domain, array $contact=[], array $status=[], array $ns=[], bool $resetPwd=false):bool{
        $domainInfo = $this->domainInfo($domain);
        //旧信息中存在禁止更新状态时，需要先清除
        $updateProhibited = in_array('clientUpdateProhibited', $domainInfo['status']);
        if ($updateProhibited){
            $eppRemoveDomain = new eppDomain($domain);
            $eppRemoveDomain->addStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateDomainRequest(new eppDomain($domain), null,$eppRemoveDomain);
            $this->request($eppRequest);
            unset($eppRemoveDomain, $eppRequest);
        }
        //$add和$remove可包含的元素ns/status/contact[admin/tech]
        //$update可包含的元素contact[registrant]/authInfo
        if (!empty($contact)){
            //把联系人ID全部转大写，便于比较
            $contact = array_map('strtoupper', $contact);
            //有注册人联系信息，说明需要变更注册人
            if (isset($contact['reg']) && !empty($contact['reg'])){
                if ($contact['reg'] != $domainInfo['contacts']['registrant']){
                    $eppUpdateDomain = new eppDomain($domain);
                    $eppUpdateDomain->setRegistrant(new eppContactHandle($contact['reg'], eppContactHandle::CONTACT_TYPE_REGISTRANT));
                }
            }
            if (isset($contact['admin']) && !empty($contact['admin'])){
                //调整contact admin节点时，先取得域名当前的contact admin id
                if ($contact['admin'] != $domainInfo['contacts']['admin']){
                    //旧联系人加入到rem节点
                    if (!isset($eppRemoveDomain) || !($eppRemoveDomain instanceof eppDomain)){
                        $eppRemoveDomain = new eppDomain($domain);
                    }
                    $eppRemoveDomain->addContact(new eppContactHandle($domainInfo['contacts']['admin'], eppContactHandle::CONTACT_TYPE_ADMIN));
                    //新联系人加入到add节点
                    if (!isset($eppAddDomain) || !($eppAddDomain instanceof eppDomain)){
                        $eppAddDomain = new eppDomain($domain);
                    }
                    $eppAddDomain->addContact(new eppContactHandle($contact['admin'], eppContactHandle::CONTACT_TYPE_ADMIN));
                }
            }
            if (isset($contact['tech']) && !empty($contact['tech'])){
                //调整contact tech节点时，先取得域名当前的contact tech id
                if ($contact['tech'] != $domainInfo['contacts']['tech']){
                    //旧联系人加入到rem节点
                    if (!isset($eppRemoveDomain) || !($eppRemoveDomain instanceof eppDomain)){
                        $eppRemoveDomain = new eppDomain($domain);
                    }
                    $eppRemoveDomain->addContact(new eppContactHandle($domainInfo['contacts']['tech'], eppContactHandle::CONTACT_TYPE_TECH));
                    //新联系人加入到add节点
                    if (!isset($eppAddDomain) || !($eppAddDomain instanceof eppDomain)){
                        $eppAddDomain = new eppDomain($domain);
                    }
                    $eppAddDomain->addContact(new eppContactHandle($contact['tech'], eppContactHandle::CONTACT_TYPE_TECH));
                }
            }
        }
        if (count($ns) > 0){
            $ns = array_map('strtoupper', $ns);
            if (count($ns)!=count($domainInfo['ns']) || array_diff($domainInfo['ns'], $ns)){
                //移除旧ns
                if (!isset($eppRemoveDomain) || !($eppRemoveDomain instanceof eppDomain)){
                    $eppRemoveDomain = new eppDomain($domain);
                }
                foreach($domainInfo['ns'] as $host){
                    $eppRemoveDomain->addHost(new eppHost($host));
                }
                //添加新ns
                if (!isset($eppAddDomain) || !($eppAddDomain instanceof eppDomain)){
                    $eppAddDomain = new eppDomain($domain);
                }
                foreach($ns as $host){
                    $eppAddDomain->addHost(new eppHost($host));
                }
            }
        }
        if (count($status) > 0){
            if (in_array('ok', $status) && count($status)>1){
                throw new eppException('The OK status is not allowed to be combined with other status.');
            }
            //先提取原状态，只保留client开头的状态
            foreach($domainInfo['status'] as $key => $item){
                if (!in_array($item, ['clientDeleteProhibited','clientUpdateProhibited','clientRenewProhibited','clientTransferProhibited','clientHold'])){
                    unset($domainInfo['status'][$key]);
                }
            }
            //取旧状态与新状态的差集
            $statusDiff = array_merge(array_diff($domainInfo['status'], $status), array_diff($status, $domainInfo['status']));
            if (!empty($statusDiff)){
                if (count($domainInfo['status']) > 0){
                    //移除旧client status
                    if(!isset($eppRemoveDomain) || !($eppRemoveDomain instanceof eppDomain)){
                        $eppRemoveDomain = new eppDomain($domain);
                    }
                    foreach($domainInfo['status'] as $statusItem){
                        $eppRemoveDomain->addStatus($statusItem);
                    }
                }
                //添加新client status
                if ($status[0] != 'ok'){
                    if(!isset($eppAddDomain) || !($eppAddDomain instanceof eppDomain)){
                        $eppAddDomain = new eppDomain($domain);
                    }
                    foreach($status as $statusItem){
                        $eppAddDomain->addStatus($statusItem);
                    }
                }
            }
        }
        if ($resetPwd){
            $password = $this->generatePassword(12);
            if (!isset($eppUpdateDomain) || !($eppUpdateDomain instanceof eppDomain)){
                $eppUpdateDomain = new eppDomain($domain);
            }
            $eppUpdateDomain->setAuthorisationCode($password);
        }
        $eppDomain = new eppDomain($domain);
        $eppRequest = new verisignEppUpdateDomainRequest($eppDomain, $eppAddDomain ?? null, $eppRemoveDomain ?? null, $eppUpdateDomain ?? null);
        $this->request($eppRequest);
        //恢复去掉的状态
        if ($updateProhibited){
            $eppAddDomain = new eppDomain($domain);
            $eppAddDomain->addStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateDomainRequest(new eppDomain($domain), $eppAddDomain);
            $this->request($eppRequest);
            unset($eppAddDomain, $eppRequest);
        }
        return true;
    }
    /**
     * @inheritDoc
     */
    public function domainTransfer(string $op, string $domain, string $password='', int $period=1, string $rnvc=null, string $dnvc=null):array{
        $eppDomain = new eppDomain($domain);
        //发起转入请求时，检查域名状态和验证码完善情况
        if ($op == eppTransferRequest::OPERATION_REQUEST){
            $domainInfo = $this->domainInfo($domain, $password);
            //旧信息中存在禁止更新状态时，需要先清除
            if(in_array('clientTransferProhibited', $domainInfo['status']) || in_array('serverTransferProhibited', $domainInfo['status'])){
                throw new eppException('Unable to transfer domain information, the current status is not allowed.');
            }
            //检查是否缺失命名和实名验证码，缺哪个补哪个，不缺不补
            if (!empty($domainInfo['verify_codes'])){
                if ($domainInfo['verify_codes']['status'] == 'compliant'){
                    $rnvc = null;
                    $dnvc = null;
                }elseif ($domainInfo['verify_codes']['status'] == 'pendingCompliance'){
                    foreach ($domainInfo['verify_codes']['profile'] as $verifyCodeProfile){
                        if ($verifyCodeProfile['status'] == 'pendingCompliance'){
                            if (!empty($verifyCodeProfile['set'])){
                                foreach ($verifyCodeProfile['set'] as $verifyCodeProfileSet){
                                    if ($verifyCodeProfileSet['type'] == 'domain'){
                                        $dnvc = null;
                                    }elseif ($verifyCodeProfileSet['type'] == 'real-name'){
                                        $rnvc = null;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            //只有发起转入时才需要密码和设置转移期限
            $eppDomain->setAuthorisationCode($password);
            $eppDomain->setPeriod($period);
        }
        $eppRequest = new verisignEppTransferDomainRequest($op, $eppDomain, $rnvc, $dnvc, $this->isIDN($domain)?'CHI':'ENG');
        $this->request($eppRequest);
        $result = [];
        $result['name'] = $this->response->getDomainName();
        $result['status'] = $this->response->getTransferStatus();
        $result['request_date'] = $this->response->getTransferRequestDate();
        $result['expired_date'] = $this->response->getTransferExpirationDate();
        $result['action_date'] = $this->response->getTransferActionDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function domainRestore(string $op, string $domain, string $expire = null, string $reason = null):bool{
        $eppRequest = new verisignEppRestoreDomainRequest($op, new eppDomain($domain), $expire, $reason);
        $this->request($eppRequest);
        return true;
    }
    /**
     * @inheritDoc
     */
    public function domainRealName(string $domain, string $rnvc, string $dnvc = null):bool{
        $domainInfo = $this->domainInfo($domain);
        //旧信息中存在禁止更新状态时，需要先清除
        $updateProhibited = in_array('clientUpdateProhibited', $domainInfo['status']);
        if ($updateProhibited){
            $eppRemoveDomain = new eppDomain($domain);
            $eppRemoveDomain->addStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateDomainRequest(new eppDomain($domain), null,$eppRemoveDomain);
            $this->request($eppRequest);
            unset($eppRemoveDomain, $eppRequest);
        }
        $eppRequest = new verisignEppRealNameDomainRequest(new eppDomain($domain), $rnvc, $dnvc);
        $this->request($eppRequest);
        //恢复去掉的状态
        if ($updateProhibited){
            $eppAddDomain = new eppDomain($domain);
            $eppAddDomain->addStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateDomainRequest(new eppDomain($domain), $eppAddDomain);
            $this->request($eppRequest);
            unset($eppAddDomain, $eppRequest);
        }
        return true;
    }
    /**
     * @inheritDoc
     */
    public function contactCheck(string $contactId){
        $eppRequest = new verisignEppCheckContactRequest(new eppContactHandle($contactId));
        $this->request($eppRequest);
        $result = $this->response->getCheckedContacts();
        return $result[$contactId];
    }
    /**
     * @inheritDoc
     */
    public function contactInfo(string $contactId, string $password=null):array{
        $eppContact = new eppContactHandle($contactId);
        if (!empty($password)){
            $eppContact->setPassword($password);
        }
        $eppRequest = new verisignEppInfoContactRequest($eppContact);
        $this->request($eppRequest);
        $result = [];
        $result['id'] = $this->response->getContactId();
        $result['roid'] = $this->response->getContactRoid();
        $result['status'] = $this->response->getContactStatus();
        $result['postal']['type'] = $this->response->getContactPostalType();
        $result['postal']['name'] = $this->response->getContactName();
        $result['postal']['org'] = $this->response->getContactCompanyname();
        $result['postal']['country'] = $this->response->getContactCountrycode();
        $result['postal']['province'] = $this->response->getContactProvince();
        $result['postal']['city'] = $this->response->getContactCity();
        $result['postal']['street'] = $this->response->getContactStreet();
        $result['postal']['zipcode'] = $this->response->getContactZipcode();
        $result['voice'] = $this->response->getContactVoice();
        $result['fax'] = $this->response->getContactFax();
        $result['email'] = $this->response->getContactEmail();
        $result['password'] = $this->response->getContactAuthInfo();
        $result['create_date'] = $this->response->getContactCreateDate();
        $result['update_date'] = $this->response->getContactUpdateDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function contactCreate(string $name, string $province, string $city, string $street, string $zipcode, string $voice, string $org = null, string $type = 'int', string $email = null, string $fax = null, string $password=null):array{
        $eppContact = new eppContact(new eppContactPostalInfo($name, $city, 'CN', $org, $street, $province, $zipcode, $type), $email, '+86.'.$voice, $fax?('+86.'.$fax):null);
        if (empty($password)){
            $password = $this->generatePassword(12);
        }
        $eppContact->setPassword($password);
        $eppRequest = new verisignEppCreateContactRequest($eppContact);
        $this->request($eppRequest);
        $result['id'] = $this->response->getContactId();
        $result['password'] = $password;
        $result['create_date'] = $this->response->getContactCreateDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function contactDelete(string $contactId):bool{
        //先取得对应联系人的信息，用于判断状态是否允许删除
        $contactInfo = $this->contactInfo($contactId);
        if (in_array('linked', $contactInfo['status']) || in_array('clientDeleteProhibited', $contactInfo['status']) || in_array('serverDeleteProhibited', $contactInfo['status'])){
            throw new eppException('Contact cannot be deleted, status is not allowed.');
        }
        $eppRequest = new verisignEppDeleteContactRequest(new eppContactHandle($contactId));
        $this->request($eppRequest);
        return true;
    }
    /**
     * @inheritDoc
     */
    public function contactUpdate(string $contactId, array $postal=[], string $voice=null, string $email = null, string $fax = null, array $status=[]):bool{
        $contactInfo = $this->contactInfo($contactId);
        //旧信息中存在禁止更新状态时，需要先清除
        $updateProhibited = in_array('clientUpdateProhibited', $contactInfo['status']);
        if ($updateProhibited){
            $eppRemoveContact = new eppContact();
            $eppRemoveContact->setStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateContactRequest($contactId, null, $eppRemoveContact);
            $this->request($eppRequest);
            unset($eppRemoveContact, $eppRequest);
        }
        //$add和$remove可包含的元素status
        //$update可包含的元素postalInfo/voice/fax/email/authInfo
        if (count($status) > 0){
            if (in_array('ok', $status) && count($status)>1){
                throw new eppException('The OK status is not allowed to be combined with other status.');
            }
            //先提取原状态，只保留client开头的状态
            foreach($contactInfo['status'] as $key => $item){
                if (!in_array($item, ['clientDeleteProhibited','clientUpdateProhibited','clientTransferProhibited'])){
                    unset($contactInfo['status'][$key]);
                }
            }
            //取旧状态与新状态的差集
            $statusDiff = array_merge(array_diff($contactInfo['status'], $status), array_diff($status, $contactInfo['status']));
            if (!empty($statusDiff)){
                if (count($contactInfo['status']) > 0){
                    //移除旧client status
                    if(!isset($eppRemoveContact) || !($eppRemoveContact instanceof eppContact)){
                        $eppRemoveContact = new eppContact();
                    }
                    foreach($contactInfo['status'] as $statusItem){
                        $eppRemoveContact->setStatus($statusItem);
                    }
                }
                //添加新client status
                if ($status[0] != 'ok'){
                    if(!isset($eppAddContact) || !($eppAddContact instanceof eppContact)){
                        $eppAddContact = new eppContact();
                    }
                    foreach($status as $statusItem){
                        $eppAddContact->setStatus($statusItem);
                    }
                }
            }
        }
        if (count($postal) > 0){
            //邮政信息更新时，不需要更新的字段要用旧信息填充
            $eppContactPostal = new eppContactPostalInfo();
            if (isset($postal['type']) && !empty($postal['type']) && strcasecmp($postal['type'], $contactInfo['postal']['type'])!=0){
                $eppContactPostal->setType($postal['type']);
            }else{
                $eppContactPostal->setType($contactInfo['postal']['type']);
            }
            if (isset($postal['name']) && !empty($postal['name']) && strcasecmp($postal['name'], $contactInfo['postal']['name'])!=0){
                $eppContactPostal->setName($postal['name']);
            }else{
                $eppContactPostal->setName($contactInfo['postal']['name']);
            }
            if (isset($postal['org']) && !empty($postal['org']) && strcasecmp($postal['org'], $contactInfo['postal']['org'])!=0){
                $eppContactPostal->setOrganisationName($postal['org']);
            }else{
                $eppContactPostal->setOrganisationName($contactInfo['postal']['org']);
            }
            if (isset($postal['province']) && !empty($postal['province']) && strcasecmp($postal['province'], $contactInfo['postal']['province'])!=0){
                $eppContactPostal->setProvince($postal['province']);
            }else{
                $eppContactPostal->setProvince($contactInfo['postal']['province']);
            }
            if (isset($postal['city']) && !empty($postal['city']) && strcasecmp($postal['city'], $contactInfo['postal']['city'])!=0){
                $eppContactPostal->setCity($postal['city']);
            }else{
                $eppContactPostal->setCity($contactInfo['postal']['city']);
            }
            if (isset($postal['street']) && !empty($postal['street']) && strcasecmp($postal['street'], $contactInfo['postal']['street'])!=0){
                $eppContactPostal->addStreet($postal['street']);
            }else{
                $eppContactPostal->addStreet($contactInfo['postal']['street']);
            }
            if (isset($postal['zipcode']) && !empty($postal['zipcode']) && strcasecmp($postal['zipcode'], $contactInfo['postal']['zipcode'])!=0){
                $eppContactPostal->setZipcode($postal['zipcode']);
            }else{
                $eppContactPostal->setZipcode($contactInfo['postal']['zipcode']);
            }
            $eppContactPostal->setCountrycode('CN');
            $eppUpdateContact = new eppContact();
            $eppUpdateContact->addPostalInfo($eppContactPostal);
        }
        if (!empty($voice) && $voice!=substr($contactInfo['voice'], 3)){
            if(!isset($eppUpdateContact) || !($eppUpdateContact instanceof eppContact)){
                $eppUpdateContact = new eppContact();
            }
            $eppUpdateContact->setVoice('+86.'.$voice);
        }
        if (!empty($fax) && $fax!=substr($contactInfo['fax'], 3)){
            if(!isset($eppUpdateContact) || !($eppUpdateContact instanceof eppContact)){
                $eppUpdateContact = new eppContact();
            }
            $eppUpdateContact->setFax('+86.'.$fax);
        }
        if (!empty($email) && strcasecmp($email, $contactInfo['email'])!=0){
            if(!isset($eppUpdateContact) || !($eppUpdateContact instanceof eppContact)){
                $eppUpdateContact = new eppContact();
            }
            $eppUpdateContact->setEmail($email);
        }
        $eppRequest = new verisignEppUpdateContactRequest($contactId, $eppAddContact ?? null, $eppRemoveContact ?? null, $eppUpdateContact ?? null);
        $this->request($eppRequest);
        //恢复禁止更新的状态
        if ($updateProhibited){
            $eppAddContact = new eppContact();
            $eppAddContact->setStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateContactRequest($contactId, $eppAddContact);
            $this->request($eppRequest);
            unset($eppAddContact, $eppRequest);
        }
        return true;
    }
    /**
     * @inheritDoc
     */
    public function contactTransfer(string $op, string $contactId, string $password):void{
        //实际业务场景中，联系人不存在转移的情况，所以不实现此接口
        throw new eppException('The TRANSFER command cannot be used for contacts.');
    }
    /**
     * @inheritDoc
     */
    public function nsCheck(string $host){
        $eppRequest = new verisignEppCheckHostRequest(new eppHost($host));
        $this->request($eppRequest);
        $result = $this->response->getCheckedHosts();
        return $result[$host];
    }
    /**
     * @inheritDoc
     */
    public function nsInfo(string $host):array{
        $eppRequest = new verisignEppInfoHostRequest(new eppHost($host));
        $this->request($eppRequest);
        $result = [];
        $result['name'] = $this->response->getHostName();
        $result['roid'] = $this->response->getHostRoid();
        $result['ip'] = $this->response->getHostAddresses();
        $result['status'] = $this->response->getHostStatuses();
        $result['create_date'] = $this->response->getHostCreateDate();
        $result['update_date'] = $this->response->getHostUpdateDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function nsCreate(string $host, array $ip):array{
        $eppHost = new eppHost($host, $ip);
        $eppRequest = new verisignEppCreateHostRequest($eppHost);
        $this->request($eppRequest);
        $result['name'] = $this->response->getHostName();
        $result['create_date'] = $this->response->getHostCreateDate();
        return $result;
    }
    /**
     * @inheritDoc
     */
    public function nsDelete(string $host):bool{
        //先取得对应联系人的信息，用于判断状态是否允许删除
        $hostInfo = $this->nsInfo($host);
        if (in_array('linked', $hostInfo['status']) || in_array('clientDeleteProhibited', $hostInfo['status']) || in_array('serverDeleteProhibited', $hostInfo['status'])){
            throw new eppException('Host cannot be deleted, status is not allowed.');
        }
        $eppRequest = new verisignEppDeleteHostRequest(new eppHost($host));
        $this->request($eppRequest);
        return true;
    }
    /**
     * @inheritDoc
     */
    public function nsUpdate(string $host, string $name=null, array $ip=[], array $status=[]):bool{
        $nsInfo = $this->nsInfo($host);
        //旧信息中存在禁止更新状态时，需要先清除
        $updateProhibited = in_array('clientUpdateProhibited', $nsInfo['status']);
        if ($updateProhibited){
            $eppRemoveHost = new eppHost($host);
            $eppRemoveHost->setHostStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateHostRequest(new eppHost($host), null, $eppRemoveHost);
            $this->request($eppRequest);
            unset($eppRemoveHost, $eppRequest);
        }
        //$add和$remove可包含的元素ip/status
        //$update可包含的元素name
        if (count($status) > 0){
            if (in_array('ok', $status) && count($status)>1){
                throw new eppException('The OK status is not allowed to be combined with other status.');
            }
            //先提取原状态，只保留client开头的状态
            foreach($nsInfo['status'] as $key => $item){
                if (!in_array($item, ['clientDeleteProhibited','clientUpdateProhibited'])){
                    unset($nsInfo['status'][$key]);
                }
            }
            //取旧状态与新状态的差集
            $statusDiff = array_merge(array_diff($nsInfo['status'], $status), array_diff($status, $nsInfo['status']));
            if (!empty($statusDiff)){
                if (count($nsInfo['status']) > 0){
                    //移除旧client status
                    if(!isset($eppRemoveHost) || !($eppRemoveHost instanceof eppHost)){
                        $eppRemoveHost = new eppHost($host);
                    }
                    foreach($nsInfo['status'] as $statusItem){
                        $eppRemoveHost->setHostStatus($statusItem);
                    }
                }
                //添加新client status
                if ($status[0] != 'ok'){
                    if(!isset($eppAddHost) || !($eppAddHost instanceof eppHost)){
                        $eppAddHost = new eppHost($host);
                    }
                    foreach($status as $statusItem){
                        $eppAddHost->setHostStatus($statusItem);
                    }
                }
            }
        }
        if (count($ip) > 0){
            $nsInfoIps = array_keys($nsInfo['ip']);
            if (count($nsInfoIps)!=count($ip) || array_diff($nsInfoIps, $ip)){
                //移除旧ns
                if (!isset($eppRemoveHost) || !($eppRemoveHost instanceof eppHost)){
                    $eppRemoveHost = new eppHost($host);
                }
                foreach($nsInfoIps as $ipAddr){
                    $eppRemoveHost->setIpAddress($ipAddr);
                }
                //添加新ns
                if (!isset($eppAddHost) || !($eppAddHost instanceof eppHost)){
                    $eppAddHost = new eppHost($host);
                }
                foreach($ip as $ipAddr){
                    $eppAddHost->setIpAddress($ipAddr);
                }
            }
        }
        if (!empty($name) && strcasecmp($name, $nsInfo['name'])!=0){
            if(!isset($eppUpdateHost) || !($eppUpdateHost instanceof eppHost)){
                $eppUpdateHost = new eppHost($name);
            }
        }
        $eppRequest = new verisignEppUpdateHostRequest(new eppHost($host), $eppAddHost ?? null, $eppRemoveHost ?? null, $eppUpdateHost ?? null);
        $this->request($eppRequest);
        //恢复禁止更新状态
        if ($updateProhibited){
            $eppAddHost = new eppHost($host);
            $eppAddHost->setHostStatus('clientUpdateProhibited');
            $eppRequest = new verisignEppUpdateHostRequest(new eppHost($host), $eppAddHost);
            $this->request($eppRequest);
            unset($eppAddHost, $eppRequest);
        }
        return true;
    }
}